/**********************************************************
 * Copyright © 2024 Walkline Wang (https://walkline.wang)
 * Gitee: https://gitee.com/walkline/keypad-for-ops
 **********************************************************/
#include "keypad.h"
#include <algorithm>

void KEYPAD::setup(void) {
#if DEBUG_PROMPT
    Serial.println("KeyPad Setup");
#endif

    if (load_settings()) {
        key_layer = settings.layer;
        light_effect = settings.light_effect;
        light_speed = settings.light_speed;
    } else {
        settings.status = SETTINGS_OK;
        settings.layer = key_layer;
        settings.light_effect = light_effect;
        settings.light_speed = light_speed;
        save_settings();
    }

#if DEBUG_PROMPT
    Serial.print("- current layer: ");
    Serial.println(key_layer);
    Serial.print("- light effect: ");
    Serial.println(light_effect);
    Serial.print("- light speed: ");
    Serial.println(light_speed);
#endif

    _74HC165::setup();
    CH9329::setup();

    memset(last_key_status, 0xff, IO_COUNT / 8);
}

uint8_t KEYPAD::get_keycode(uint8_t index) { return KEY_MAP[key_layer][index]; }

uint8_t KEYPAD::get_layer_index(uint8_t index) {
    return uint8_t(std::find(KEY_MAP[0], std::end(KEY_MAP[0]), index) -
                   KEY_MAP[0]);
}

uint8_t KEYPAD::key_status(uint8_t index) {
    return bitRead(last_key_status[index / 8], index % 8);
}

bool KEYPAD::key_changed(uint8_t index, uint8_t &status) {
    status = bitRead(key_buffer[index / 8], index % 8);
    bool changed = status != bitRead(last_key_status[index / 8], index % 8);

    if (changed) {
        status ? bitSet(last_key_status[index / 8], index % 8)
               : bitClear(last_key_status[index / 8], index % 8);
    }

    return changed;
}

uint8_t KEYPAD::get_keycode_type(uint8_t keycode) {
    if (keycode == RESERVED || keycode == KB_SEPARATOR ||
        keycode == MS_SEPARATOR || keycode == KB_CONTROL_KEYS_SEPARATOR) {
        return NONE;
    }

    return keycode < KB_SEPARATOR ? KEYBOARD : NONE;
}

uint8_t KEYPAD::process_hid_data(bool status, uint8_t keycode) {
    uint8_t keycode_type = get_keycode_type(keycode);

    switch (keycode_type) {
    case KEYBOARD:
        status ? unpress(keycode) : press(keycode);
        break;
    case NONE:
        break;
    }

    return keycode_type;
}

void KEYPAD::send_kb_hid_data(bool kb_changed) {
    if (kb_changed) {
        send_kb_data();
    }
}

/********************
 * private functions
 ********************/
void KEYPAD::press(uint8_t keycode) {
    if (keycode < KB_SEPARATOR &&
        keycode > KB_CONTROL_KEYS_SEPARATOR) { /* controls keys */
        bitSet(hid_kb_data[0], keycode - KB_CONTROL_KEYS_SEPARATOR - 1);
    } else {
        uint8_t index;

        if (keycode_exist(keycode, index) || index >= HID_KB_MAX) {
            return;
        }

        hid_kb_data[index] = keycode;
    }
}

void KEYPAD::unpress(uint8_t keycode) {
    if (keycode < KB_SEPARATOR &&
        keycode > KB_CONTROL_KEYS_SEPARATOR) { /* controls keys */
        bitClear(hid_kb_data[0], keycode - KB_CONTROL_KEYS_SEPARATOR - 1);
    } else {
        uint8_t index;

        if (!keycode_exist(keycode, index)) {
            return;
        }

        hid_kb_data[index] = 0x00;
    }
}

void KEYPAD::release_all() {
    memset(hid_kb_data, 0x00, HID_KB_MAX);
    send_kb_data();
}

bool KEYPAD::keycode_exist(uint8_t keycode, uint8_t &index) {
    bool found = std::find(hid_kb_data + 2, std::end(hid_kb_data), keycode) !=
                 std::end(hid_kb_data);

    if (!found) {
        keycode = 0x00;
    }

    index = uint8_t(std::find(hid_kb_data + 2, std::end(hid_kb_data), keycode) -
                    hid_kb_data);

    return found;
}

void KEYPAD::send_kb_data() {
    send_hid_data(CMD_SEND_KB_GENERAL_DATA, hid_kb_data, HID_KB_MAX);
}

void KEYPAD::switch_effect() {
    light_effect = (light_effect + 1) % LIGHT_EFFECT_COUNT;

    settings.light_effect = light_effect;
    save_settings();

    Serial.print("=== switch light effect to ");
    Serial.print(light_effect);
    Serial.println(" ===");
}
